#ifndef FNT_LOADTHREADSHARED_H
#define FNT_LOADTHREADSHARED_H
#include <unordered_map>
#include <functional>
#include <QString>
#include <mutex>
#include <unordered_set>
#include "annot.tree.h"
//#include "tracer.h"
#include "cubedata.h"
#include <array>
#include "utils.h"
template<typename T>
inline void hash_combine(std::size_t& seed, const T& val)
{
	seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}

template<typename T>
inline void hash_val(std::size_t& seed, const T& val)
{
	hash_combine(seed, val);
}

template<typename T, typename... Types>
inline void hash_val(std::size_t& seed, const T& val, const Types&... args)
{
	hash_combine(seed, val);
	hash_val(seed, args...);
}

template<typename... Types>
inline std::size_t hash_val(const Types& ...args)
{
	std::size_t seed = 0;
	hash_val(seed, args...);
	return seed;
}
struct LocationString {
	bool isPat;
	std::string location;
};
struct Location {
	int id;
	bool isPat; //local path
	bool isUrl; //url
	QString location;
};
struct CubeData;

class Hasher {
public:
	size_t operator()(const LocationString& location) const {
		//calculate hash here.
		return hash_val(
			location.isPat,
			location.location);
	}
};
class Equal
{
public:
	bool operator()(const LocationString& location, const LocationString&location2) const {
		if (
			location.isPat == location2.isPat &&
			location.location == location2.location ) {
			return true;
		}
		return false;
	}
};

struct LoadThreadShared {

	//std::unordered_map<std::pair<bool, QString>, Location*> locations;
	std::unordered_map<LocationString, Location*,Hasher,Equal> locations;
	std::mutex mtx_loc;

	std::unordered_set<CubeData*> cubes;
	std::mutex mtx_cubes;
	std::condition_variable cv_cubes;


	std::unordered_map<std::string, AnnotTree*> annotTrees;
	std::mutex mtx_annots;
	std::condition_variable cv_annots;

	LoadThreadShared() :
		locations{}, mtx_loc{},
		cubes{}, mtx_cubes{}, cv_cubes{},
		annotTrees{}, mtx_annots{}, cv_annots{}
	{
	}
	~LoadThreadShared() {
		if (!cubes.empty())
			printMessage("Cube data memory leak!");
		for (auto& p : locations)
			delete p.second;
		for (auto& p : annotTrees)
			delete p.second;
	}

	Location* getLocation(bool isPat, bool isUrl, const QString& loc) {
		if (loc.isEmpty())
			return nullptr;
		std::unique_lock<std::mutex> lck{ mtx_loc };
		auto i = locations.find({isPat,  loc.toStdString() });
		if (i != locations.end())
			return i->second;
		int id = locations.size();
		return locations[{isPat, loc.toStdString()}] = new Location{ id, isPat, isUrl, loc };
	}

	AnnotTree* getAnnot(const QString& loc) {
		if (loc.isEmpty())
			return nullptr;
		std::unique_lock<std::mutex> lck{ mtx_annots };
		auto i = annotTrees.find(loc.toStdString());
		if (i != annotTrees.end())
			return i->second;
		int id = annotTrees.size();
		return annotTrees[loc.toStdString()] = new AnnotTree{ id, loc };
	}

	CubeDataRef getCube(const CubeId& id) {
		std::unique_lock<std::mutex> lck{ mtx_cubes };
		for (auto cube : cubes) {
			if (cube->id == id) {
				cube->refc++;
				return CubeDataRef{ cube };
			}
		}
		auto cube = new CubeData{ this, id };
		cube->refc++;
		cubes.insert(cube);
		return CubeDataRef{ cube };
	}
	void ref(CubeData* c) {
		std::unique_lock<std::mutex> lck{ mtx_cubes };
		c->refc++;
	}
	void unref(CubeData* c) {
		std::unique_lock<std::mutex> lck{ mtx_cubes };
		c->refc--;
		if (c->refc <= 0) {
			cubes.erase(c);
			delete c;
		}
	}
	CubeData::State changeState(CubeData* c, int i, CubeData::State prev, CubeData::State s) {
		std::unique_lock<std::mutex> lck{ mtx_cubes };
		if (c->states[i] == prev) {
			c->states[i] = s;
			if (prev == CubeData::State::Reading && (s == CubeData::State::Ready || s == CubeData::State::Error))
				cv_cubes.notify_all();
			return prev;
		}
		else {
			return c->states[i];
		}
	}
	bool alloc(CubeData* c, CubeType type, int64_t w, int64_t h, int64_t d) {
		std::unique_lock<std::mutex> lck{ mtx_cubes };
		if (c->data)
			return true;
		try {
			c->xsize = w;
			c->ysize = h;
			c->zsize = d;
			c->type = type;
			c->widthAdj = (c->id.nx*c->xsize + 7) / 8 * 8;
			c->heightAdj = c->id.ny*c->ysize;
			c->depthAdj = c->id.nz*c->zsize;
			c->data = new char[bytes_per_voxel(type)*c->widthAdj*c->heightAdj*c->depthAdj];
		}
		catch (...) {
			delete c->data;
			return false;
		}
		return true;
	}
	void waitForCube(CubeData* c, int i) {
		std::unique_lock<std::mutex> lck{ mtx_cubes };
		while (c->states[i] == CubeData::State::Reading)
			cv_cubes.wait(lck);
	}
};
#endif // !FNT_LOADTHREADSHARED_H
